Fix ty invalid assignment - #15222
Conversation
df2fe0d to
2d7f5d2
Compare
|
@priya-sundaram-dev your review, please. |
There was a problem hiding this comment.
🟡 Changes recommended
There’s a confirmed performance regression in minimum_cut.py and an assert in doubly_linked_list.py::delete() that changes empty-list behavior (and can be stripped under -O).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR removes remaining ty invalid-assignment/type diagnostics by tightening typing and narrowing optional values across a set of existing algorithm implementations, and updates ty configuration to stop ignoring invalid-assignment.
Changes:
- Removed
ty’srules.invalid-assignment = "ignore"so invalid assignments are surfaced instead of suppressed. - Added/adjusted type narrowing (e.g.,
assert ... is not None,cast(...), explicit optional handling) in several algorithms and data structures. - Cleaned up a few typing-related imports and assignments to satisfy stricter type checking.
File summaries
| File | Description |
|---|---|
| scheduling/cpuschedulingalgorithms.py | Avoids indexing empty Treeview “values” when deleting a selected process. |
| pyproject.toml | Stops ignoring ty’s invalid-assignment rule. |
| neural_network/input_data.py | Adds explicit urllib.request import for type checking / usage clarity. |
| networking_flow/minimum_cut.py | Replaces float("inf") with an integer upper bound for path_flow initialization (but currently adds avoidable per-iteration work). |
| machine_learning/automatic_differentiation.py | Replaces defaultdict accumulation with a typed dict + explicit .get(...) defaults. |
| fractals/mandelbrot.py | Adds a non-None assertion for PIL pixel access. |
| data_structures/linked_list/singly_linked_list.py | Adds type annotation for head and asserts to narrow optionals during traversal. |
| data_structures/linked_list/doubly_linked_list.py | Adds basic typing and asserts to narrow optionals (but introduces a new AssertionError path and redundancy). |
| data_structures/heap/binomial_heap.py | Adds asserts to narrow internal optional heap pointers. |
| data_structures/binary_tree/non_recursive_segment_tree.py | Uses cast(T, None) to satisfy typing for the pre-build segment tree array. |
| cellular_automata/one_dimensional.py | Adds a non-None assertion for PIL pixel access. |
| cellular_automata/conways_game_of_life.py | Adds a non-None assertion for PIL pixel access. |
Review details
Suppressed comments (1)
data_structures/linked_list/doubly_linked_list.py:179
assert current is not Nonechanges the empty-list behavior to raiseAssertionError(and can be removed withpython -O). Prefer an explicit check that raises the sameValueErrorused for “not found”, while still narrowing the type forcurrent.dataaccess.
def delete(self, data) -> str:
current = self.head
assert current is not None
- Files reviewed: 12/12 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| while bfs(residual, source, sink, parent): | ||
| path_flow = float("inf") | ||
| path_flow = max(max(row) for row in residual) | ||
| s = sink |
| assert self.tail is not None | ||
| self.tail.next = new_node | ||
| assert self.tail is not None | ||
| new_node.previous = self.tail | ||
| self.tail = new_node |
priya-sundaram-dev
left a comment
There was a problem hiding this comment.
Reviewed all 12 files — this cleanly earns the removal of rules.invalid-assignment = "ignore" from pyproject.toml, and CI (ty/build/ruff) is green. Notes:
Correct and idiomatic
- The
Optional-narrowingassert ... is not Noneguards in the linked lists, heap, andimg.load()sites (mandelbrot,conways_game_of_life,one_dimensional) are the right call for this repo's style. non_recursive_segment_tree.py:cast(T, None)in place of theAny | Tsentinel is a nice tightening.singly_linked_list.delete_nth: bindingdelete_node = temp.next_nodethentemp.next_node = delete_node.next_nodeis equivalent to the old two-hop and reads better.neural_network/input_data.py: addingimport urllib.requestis a real correctness fix —urllib.requestwas used but onlyurllibwas imported (works only if another module happened to import the submodule first).automatic_differentiation.py: swappingdefaultdict(lambda: 0)for a typeddict+.get(param, np.zeros_like(...))preserves the accumulation semantics (first hit:zeros_like + grad == grad) while giving ty a real value type.
The one behavioural line worth calling out for other reviewers — networking_flow/minimum_cut.py:
- path_flow = float("inf")
+ path_flow = max(max(row) for row in residual)This is correct: path_flow is only ever reduced via min(path_flow, residual[u][v]) down the augmenting path, so any value >= the path's bottleneck yields the identical result, and the global max residual capacity is always such an upper bound. It's also correctly placed inside the while bfs(...) loop rather than hoisted — reverse-edge residuals grow (residual[v][u] += path_flow) across augmentations, so a value computed once before the loop would not stay a valid upper bound. The tradeoff is O(V²) per augmentation; if you'd rather keep O(1) here, sys.maxsize (an int, so no assignment-type error) would also satisfy ty while matching the original intent exactly.
Tiny nit (non-blocking): in doubly_linked_list.insert_at_nth, the elif index == length branch asserts self.tail is not None twice with no reassignment between them — the second one is redundant and can be dropped.
Nothing blocking from me — looks good to merge.
priya-sundaram-dev
left a comment
There was a problem hiding this comment.
Reviewed — this is a clean, well-scoped pass and CI is green (build / ruff / ty / pre-commit all pass). The assert x is not None narrowing before mutating linked-list / heap / segment-tree nodes is the idiomatic way to satisfy ty here without changing runtime behavior, and dropping rules.invalid-assignment = "ignore" from pyproject.toml is the right end-state once the count hits zero. LGTM.
Two small things worth a note (non-blocking):
networking_flow/minimum_cut.py:path_flow = max(max(row) for row in residual)in place offloat("inf")keepspath_flowanint(which is what silencesty), and it's still correct because the max residual capacity is an upper bound on every edge on the augmenting path, so the subsequentmin(path_flow, residual[...][...])still selects the true bottleneck. Since that equivalence isn't obvious to a future reader, a one-line# int upper bound on any single-edge capacitycomment would help.machine_learning/automatic_differentiation.py: swappingdefaultdict(lambda: 0)for a plain dict +partial_deriv.get(param, np.zeros_like(dparam_dtarget))is behavior-preserving (first-touch went from scalar0to a correctly-shaped zero array, which is actually slightly more correct for the+=). Good change.
No blockers from me.
Describe your change:
Fixed remaining
tyinvalid-assignment and related type-checking diagnostics across multiple existing files.Changes include:
urllib.request.DIRECTORY.md.Validation performed: